#include <stdio.h>

#include <libavformat/avformat.h>
#include <libavutil/samplefmt.h>
#include <libavcodec/avcodec.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>

// $ sudo apt install libsdl2-dev
#include <SDL/SDL.h>

// $ sudo apt install libsdl-image1.2-dev
#include <SDL/SDL_video.h>

// $ sudo apt install libsdl-sound1.2-dev
#include <SDL/SDL_audio.h>

static void log_callback_help(void *ptr, int level, const char *fmt, va_list vl)
{
    vfprintf(stdout, fmt, vl);
}

static uint32_t audio_len = 0;

static const  uint8_t  *audio_chunk;
static const uint8_t *audio_pos = 0;
static const uint8_t *out_buffer = NULL;

#define MAX_AUDIO_FRAME_SIZE 19200

void fill_audio(void *userdata, Uint8 *stream, int len) {
    // SDL_memset(stream, 0, len);
    if(len == 0) return;

    len = len > audio_len ? audio_len : len;
    SDL_MixAudio(stream, audio_pos, len, SDL_MIX_MAXVOLUME);

    printf("audio_len: %d, len: %d\n", audio_len, len);
    audio_pos += len;
    audio_len -= len;
}

#if 0
int main(int argc, const char *argv[]) {
    const char *file_path = "../../mp3s/xhh.mp3";

    av_register_all();

    av_log_set_level(AV_LOG_TRACE);

    int ret = 0;
    AVFormatContext *in_afc = avformat_alloc_context();
    ret = avformat_open_input(&in_afc, file_path, NULL, NULL);
    if(0 > ret){
        printf("av_format_open_input(\"%s\") ret: %s", file_path, av_err2str(ret));
        return ret;
    }

    ret = avformat_find_stream_info(in_afc, NULL);
    if(0 > ret){
        printf("avformat_find_stream_info() return: %s\n", av_err2str(ret));
        return ret;
    }

    av_dump_format(in_afc, 0, file_path, 0);

    int audio_idx = -1;
    for(int i = 0; i < in_afc->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO; i++) {
        audio_idx = i;
    }

    if(0 > audio_idx) {
        printf("No audio stream;\n");
        return -1;
    }
    AVPacket pkt;
    av_init_packet(&pkt);

    int got_audio  = 1;
    AVCodecContext *in_cctx = in_afc->streams[audio_idx]->codec;

    AVCodec *acodec = avcodec_find_decoder(in_cctx->codec_id);
    if(acodec == NULL) {
        printf("failed find audio decoder\n");
        return -1;
    }

    ret = avcodec_open2(in_cctx, acodec, NULL);
    if(0 > ret) {
        printf("Open codec(%s) failed: %s\n", acodec->name, av_err2str(ret));
        return ret;
    }

    printf("Bitrate:\t %3d\n", in_afc->bit_rate);
	printf("Decoder Name:\t %s\n", in_cctx->codec->long_name);
	printf("Channels:\t %d\n", in_cctx->channels);
	printf("Sample per Second\t %d \n", in_cctx->sample_rate);

    int out_channel_layout = AV_CH_LAYOUT_STEREO;
    int out_nb_samples = in_cctx->frame_size;
    enum AVSampleFormat out_sample_format = AV_SAMPLE_FMT_S16;
    int out_sample_rate = 44100;
    int out_channels = av_get_channel_layout_nb_channels(out_channel_layout);
    int out_buffer_size = av_samples_get_buffer_size(NULL, out_channels, out_nb_samples, out_sample_format, 1);
    out_buffer = (const uint8_t *)malloc(MAX_AUDIO_FRAME_SIZE * 2);

    int in_channel_layout = av_get_default_channel_layout(in_cctx->channels);

    AVFrame *frame = av_frame_alloc();

    if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER) < 0) {
        printf("Init SDL Error: %s\n", SDL_GetError());
        return -1;
    }

    SDL_AudioSpec spec;
    spec.freq = 44100;
    spec.format = AUDIO_S16SYS;
    spec.channels = out_channels;
    spec.silence = 0;
    spec.callback = fill_audio;
    spec.userdata = in_cctx;

    if(SDL_OpenAudio(&spec, NULL) < 0){
        printf("SDL_OpenAudio() ret Error: %s\n", SDL_GetError());
        return -1;
    }

    int index = 0;
    struct SwrContext *swr_ctx = swr_alloc();
    swr_alloc_set_opts(swr_ctx, out_channel_layout, out_sample_format, out_sample_rate, in_channel_layout, in_cctx->sample_fmt, in_cctx->sample_rate, 0, NULL);
    swr_init(swr_ctx);

    SDL_PauseAudio(0);

    while(av_read_frame(in_afc, &pkt) >= 0) {
        if(pkt.stream_index == audio_idx) {
            ret = avcodec_decode_audio4(in_cctx, frame, &got_audio, &pkt);
            if(0 > ret) {
                printf("avcodec_decode_audio4() ret error: %s\n", av_err2str(ret));
                return ret;
            }
            if(got_audio > 0) {
                swr_convert(swr_ctx, &out_buffer, MAX_AUDIO_FRAME_SIZE, (const uint8_t **)frame->data, frame->nb_samples);
                printf("index:%5d\t pts:%lld\t packet size:%d, frame nb_samples: %d\n",index, pkt.pts, pkt.size, frame->nb_samples);
                index++;
            }
            audio_chunk = out_buffer;
            audio_len = out_buffer_size;
            audio_pos = audio_chunk;
            while(audio_len > 0) {
                SDL_Delay(1);
            }
        }
        av_packet_unref(&pkt);
    }
    swr_free(&swr_ctx);
    swr_ctx = NULL;

    av_frame_free(frame);
    frame = NULL;

    av_free((void *)out_buffer);
    avcodec_free_context(&in_cctx);
    avformat_close_input(&in_afc);
}

#else
#define USE_SDL 1
 
int main(int argc, char* argv[])
{
	AVFormatContext	*pFormatCtx;
	int				i, audioStream;
	AVCodecContext	*pCodecCtx;
	AVCodec			*pCodec;
 
	char url[]="../../mp3s/xhh.mp3";
 
	av_register_all();
	avformat_network_init();
	pFormatCtx = avformat_alloc_context();
	//Open
	if(avformat_open_input(&pFormatCtx,url,NULL,NULL)!=0){
		printf("Couldn't open input stream.\n");
		return -1;
	}
	// Retrieve stream information
	if(avformat_find_stream_info(pFormatCtx, NULL)<0){
		printf("Couldn't find stream information.\n");
		return -1;
	}
	// Dump valid information onto standard error
	av_dump_format(pFormatCtx, 0, url, 0);
 
	// Find the first audio stream
	audioStream=-1;
	for(i=0; i < pFormatCtx->nb_streams; i++)
		if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO){
			audioStream=i;
			break;
		}
 
	if(audioStream==-1){
		printf("Didn't find a audio stream.\n");
		return -1;
	}
 
	// Get a pointer to the codec context for the audio stream
	pCodecCtx=pFormatCtx->streams[audioStream]->codec;
 
	// Find the decoder for the audio stream
	pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
	if(pCodec==NULL){
		printf("Codec not found.\n");
		return -1;
	}
 
	// Open codec
	if(avcodec_open2(pCodecCtx, pCodec,NULL)<0){
		printf("Could not open codec.\n");
		return -1;
	}
 
	FILE *pFile=NULL;
#if OUTPUT_PCM
	pFile=fopen("output.pcm", "wb");
#endif
 
	AVPacket *packet=(AVPacket *)malloc(sizeof(AVPacket));
	av_init_packet(packet);
 
	//Out Audio Param
	uint64_t out_channel_layout=AV_CH_LAYOUT_STEREO;
	//AAC:1024  MP3:1152
	int out_nb_samples=pCodecCtx->frame_size;
	enum AVSampleFormat out_sample_fmt=AV_SAMPLE_FMT_S16;
	int out_sample_rate=44100;
	int out_channels=av_get_channel_layout_nb_channels(out_channel_layout);
	//Out Buffer Size
	int out_buffer_size=av_samples_get_buffer_size(NULL,out_channels ,out_nb_samples,out_sample_fmt, 1);
 
	uint8_t *out_buffer=(uint8_t *)av_malloc(MAX_AUDIO_FRAME_SIZE*2);
 
	AVFrame	*pFrame;
	pFrame=av_frame_alloc();
//SDL------------------
#if USE_SDL
	//Init
	if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {  
		printf( "Could not initialize SDL - %s\n", SDL_GetError()); 
		return -1;
	}
	//SDL_AudioSpec
	SDL_AudioSpec wanted_spec;
	wanted_spec.freq = out_sample_rate; 
	wanted_spec.format = AUDIO_S16SYS; 
	wanted_spec.channels = out_channels; 
	wanted_spec.silence = 0; 
	wanted_spec.samples = out_nb_samples; 
	wanted_spec.callback = fill_audio; 
	wanted_spec.userdata = pCodecCtx; 
 
	if (SDL_OpenAudio(&wanted_spec, NULL)<0){ 
		printf("can't open audio.\n"); 
		return -1; 
	} 
#endif
	printf("Bitrate:\t %3d\n", pFormatCtx->bit_rate);
	printf("Decoder Name:\t %s\n", pCodecCtx->codec->long_name);
	printf("Channels:\t %d\n", pCodecCtx->channels);
	printf("Sample per Second\t %d \n", pCodecCtx->sample_rate);
 
	uint32_t ret,len = 0;
	int got_picture;
	int index = 0;
	//FIX:Some Codec's Context Information is missing
	int64_t in_channel_layout=av_get_default_channel_layout(pCodecCtx->channels);
	//Swr
	struct SwrContext *au_convert_ctx;
	au_convert_ctx = swr_alloc();
	au_convert_ctx=swr_alloc_set_opts(au_convert_ctx,out_channel_layout, out_sample_fmt, out_sample_rate,
		in_channel_layout,pCodecCtx->sample_fmt , pCodecCtx->sample_rate,0, NULL);
	swr_init(au_convert_ctx);
 
	//Play
	SDL_PauseAudio(0);
 
	while(av_read_frame(pFormatCtx, packet)>=0){
		if(packet->stream_index==audioStream){
 
			ret = avcodec_decode_audio4( pCodecCtx, pFrame,&got_picture, packet);
			if ( ret < 0 ) {
                printf("Error in decoding audio frame.\n");
                return -1;
            }
			if ( got_picture > 0 ){
				swr_convert(au_convert_ctx,&out_buffer, MAX_AUDIO_FRAME_SIZE,(const uint8_t **)pFrame->data , pFrame->nb_samples);
 
				printf("index:%5d\t pts:%lld\t packet size:%d\n",index,packet->pts,packet->size);
 
#if OUTPUT_PCM
				//Write PCM
				fwrite(out_buffer, 1, out_buffer_size, pFile);
#endif
				
				index++;
			}
//SDL------------------
#if USE_SDL
			//Set audio buffer (PCM data)
			audio_chunk = (Uint8 *) out_buffer; 
			//Audio buffer length
			audio_len =out_buffer_size;
 
			audio_pos = audio_chunk;
 
			while(audio_len>0)//Wait until finish
				SDL_Delay(1); 
#endif
		}
		av_free_packet(packet);
	}
 
	swr_free(&au_convert_ctx);
 
#if USE_SDL
	SDL_CloseAudio();//Close SDL
	SDL_Quit();
#endif
 
#if OUTPUT_PCM
	fclose(pFile);
#endif
	av_free(out_buffer);
	avcodec_close(pCodecCtx);
	avformat_close_input(&pFormatCtx);
 
	return 0;
}
#endif